package com.ruoyi.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.base.BaseEntity;
import com.ruoyi.common.constant.CacheKeyConstants;
import com.ruoyi.common.constant.ResultConstants;
import com.ruoyi.common.enums.AmountType;
import com.ruoyi.common.enums.TradeType;
import com.ruoyi.common.enums.TxnType;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.support.Convert;
import com.ruoyi.common.utils.BigDecimalUtil;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.mapper.WdyTradeMapper;
import com.ruoyi.system.service.*;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

/**
 * 交易 服务层实现
 * 
 * @author ruoyi
 * @date 2019-12-26
 */
@Service
public class WdyTradeServiceImpl extends ServiceImpl<WdyTradeMapper, WdyTrade> implements IWdyTradeService
{
	private static final double COMMISSION_RATE = 0.02;
	@Autowired
	private IWdyUserService wdyUserService;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private ISysConfigService sysConfigService;
	@Autowired
	private IWdyTradeRecordService wdyTradeRecordService;
	@Autowired
	private IWdyDealService wdyDealService;
	@Autowired
	private IWdyTxnHistoryService wdyTxnHistoryService;
	/**
     * 查询交易信息
     * 
     * @param id 交易ID
     * @return 交易信息
     */
    @Override
	public WdyTrade selectWdyTradeById(Long id)
	{
	    return baseMapper.selectWdyTradeById(id);
	}
	
	/**
     * 查询交易列表
     * 
     * @param wdyTrade 交易信息
     * @return 交易集合
     */
	@Override
	public List<WdyTrade> selectWdyTradeList(WdyTrade wdyTrade)
	{
	    return baseMapper.selectWdyTradeList(wdyTrade);
	}
	
    /**
     * 新增交易
     * 
     * @param wdyTrade 交易信息
     * @return 结果
     */
	@Override
	public int insertWdyTrade(WdyTrade wdyTrade)
	{
	    return baseMapper.insertWdyTrade(wdyTrade);
	}
	
	/**
     * 修改交易
     * 
     * @param wdyTrade 交易信息
     * @return 结果
     */
	@Override
	public int updateWdyTrade(WdyTrade wdyTrade)
	{
	    return baseMapper.updateWdyTrade(wdyTrade);
	}

	/**
     * 删除交易对象
     * 
     * @param ids 需要删除的数据ID
     * @return 结果
     */
	@Override
	public int deleteWdyTradeByIds(String ids)
	{
		return baseMapper.deleteWdyTradeByIds(Convert.toStrArray(ids));
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public void trade(WdyTrade trade) {
		Integer tradeType = trade.getTradeType();
		if(tradeType == null) {
			throw new BusinessException(ResultConstants.FI1000, "tradeType " + MessageUtils.message("not.null"));
		}
		if(tradeType.compareTo(TradeType.BUY.getCode()) != 0
				&& tradeType.compareTo(TradeType.SELL.getCode()) != 0) {
			throw new BusinessException(ResultConstants.FI1000, "tradeType " + MessageUtils.message("param.error"));
		}

		if(trade.getPrice() == null) {
			throw new BusinessException(ResultConstants.FI1000, "price " + MessageUtils.message("not.null"));
		}
		BigDecimal price = BigDecimalUtil.getP3Decimal(trade.getPrice());
		trade.setPrice(price);
		if(price.compareTo(BigDecimal.ZERO) <= 0) {
			throw new BusinessException(ResultConstants.FI1000, "price" + MessageUtils.message("param.error"));
		}

		if(tradeType.compareTo(TradeType.SELL.getCode()) == 0) {
			BigDecimal minSellPrice = new BigDecimal(sysConfigService.selectConfigByKey("min_sell_price"));
			if(price.compareTo(minSellPrice) < 0) {
				throw new BusinessException(ResultConstants.FI1000, String.format("当前最低卖价%s", minSellPrice));
			}
		}

		if(trade.getVc() == null) {
			throw new BusinessException(ResultConstants.FI1000, "vc " + MessageUtils.message("not.null"));
		}
		BigDecimal vc = BigDecimalUtil.getP4Decimal(trade.getVc());
		trade.setVc(vc);
		if(vc.compareTo(BigDecimal.ZERO) <= 0) {
			throw new BusinessException(ResultConstants.FI1000, "vc" + MessageUtils.message("param.error"));
		}

		QueryWrapper<WdyUser> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("user_id", trade.getUserId());
		queryWrapper.eq("del_flag", BaseEntity.NOT_DELETED);
		WdyUser user = wdyUserService.getOne(queryWrapper);


		if(tradeType == TradeType.BUY.getCode()) {
			BigDecimal cnn = BigDecimalUtil.getP3Decimal(trade.getPrice().multiply(trade.getVc()));
			if(cnn.compareTo(user.getCnnAmount()) > 0) {
				throw new BusinessException(ResultConstants.EX9110, "CNN余额不足");
			}
			WdyUser wdyUser = new WdyUser();
			wdyUser.setCnnAmount(user.getCnnAmount().subtract(cnn));
			wdyUser.setVersion(user.getVersion());
			if(!wdyUserService.update(wdyUser, queryWrapper)) {
				throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
			}

			trade.setTradingCnn(cnn);
			trade.setTradingVc(trade.getVc());
		} else if(tradeType == TradeType.SELL.getCode()) {
			if(trade.getVc().compareTo(user.getVcAmount()) > 0) {
				throw new BusinessException(ResultConstants.EX9110, "VC数量不足");
			}
			WdyUser wdyUser = new WdyUser();
			wdyUser.setVcAmount(user.getVcAmount().subtract(trade.getVc()));
			wdyUser.setVersion(user.getVersion());
			if(!wdyUserService.update(wdyUser, queryWrapper)) {
				throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
			}
			trade.setTradingVc(trade.getVc());
		}
		this.save(trade);
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public void match(Long id) {
		WdyTrade trade = this.getById(id);

		BigDecimal vcPrice = BigDecimal.ZERO;
		QueryWrapper<WdyDeal> queryWrapper = new QueryWrapper<>();
		queryWrapper.orderByDesc("id");
		queryWrapper.last("limit 1");
		WdyDeal wdyDeal = wdyDealService.getOne(queryWrapper);
		if(wdyDeal != null) {
			vcPrice = wdyDeal.getPrice();
		}

		if(trade.getTradeType() == TradeType.BUY.getCode()) {
			handleBuy(trade, vcPrice);
		} else {
			handleSell(trade, vcPrice);
		}
	}

	private WdyDeal makeDeal(WdyTrade buyTrade, WdyTrade sellTrade, BigDecimal vcPrice, int tradeType) {
		BigDecimal dealVc = sellTrade.getTradingVc();
		if(dealVc.compareTo(buyTrade.getTradingVc()) > 0) {
			dealVc = buyTrade.getTradingVc();
		}
		sellTrade.setTradingVc(sellTrade.getTradingVc().subtract(dealVc));
		sellTrade.setTradedVc(sellTrade.getTradedVc().add(dealVc));
		buyTrade.setTradingVc(buyTrade.getTradingVc().subtract(dealVc));
		buyTrade.setTradedVc(buyTrade.getTradedVc().add(dealVc));

		if(buyTrade.getPrice().compareTo(sellTrade.getPrice()) == 0) {
			vcPrice = buyTrade.getPrice();
		} else if(vcPrice.compareTo(sellTrade.getPrice()) <= 0) {
			vcPrice = sellTrade.getPrice();
		} else if(vcPrice.compareTo(buyTrade.getPrice()) >= 0) {
			vcPrice = buyTrade.getPrice();
		}

		BigDecimal dealCnn = BigDecimalUtil.getP3Decimal(dealVc.multiply(vcPrice));
		sellTrade.setTradedCnn(sellTrade.getTradedCnn().add(dealCnn));
		buyTrade.setTradedCnn(buyTrade.getTradedCnn().add(dealCnn));
		buyTrade.setTradingCnn(buyTrade.getTradingCnn().subtract(dealCnn));


		BigDecimal cnnCommission = BigDecimalUtil.getP3Decimal(dealCnn.multiply(BigDecimal.valueOf(COMMISSION_RATE)));
		BigDecimal actualCnn = dealCnn.subtract(cnnCommission);
		BigDecimal vcCommission = BigDecimalUtil.getP3Decimal(dealVc.multiply(BigDecimal.valueOf(COMMISSION_RATE)));
		BigDecimal actualVc = dealVc.subtract(vcCommission);

		QueryWrapper<WdyUser> buyUserQuery = new QueryWrapper<>();
		buyUserQuery.eq("user_id", buyTrade.getUserId());
		WdyUser buyUser = wdyUserService.getOne(buyUserQuery);
		WdyUser updateUser = new WdyUser();
		updateUser.setId(buyUser.getId());
		updateUser.setVcAmount(buyUser.getVcAmount().add(actualVc));
		updateUser.setVersion(buyUser.getVersion());
		if(!wdyUserService.updateById(updateUser)) {
			throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
		}
		QueryWrapper<WdyUser> sellUserQuery = new QueryWrapper<>();
		sellUserQuery.eq("user_id", sellTrade.getUserId());
		WdyUser sellUser = wdyUserService.getOne(sellUserQuery);
		updateUser = new WdyUser();
		updateUser.setId(sellUser.getId());
		updateUser.setCnnWithdrawAmount(sellUser.getCnnWithdrawAmount().add(actualCnn));
		updateUser.setVersion(sellUser.getVersion());
		if(!wdyUserService.updateById(updateUser)) {
			throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
		}

		this.updateById(buyTrade);
		this.updateById(sellTrade);

		WdyDeal wdyDeal = new WdyDeal();
		wdyDeal.setPrice(vcPrice);
		wdyDeal.setAmount(dealVc);
		wdyDeal.setTradeType(tradeType);
		wdyDealService.save(wdyDeal);

		WdyTradeRecord buyRecord = new WdyTradeRecord();
		buyRecord.setUserId(buyTrade.getUserId());
		buyRecord.setTradeId(buyTrade.getId());
		buyRecord.setTradeType(buyTrade.getTradeType());
		buyRecord.setDealId(wdyDeal.getId());
		wdyTradeRecordService.save(buyRecord);

		WdyTradeRecord sellRecord = new WdyTradeRecord();
		sellRecord.setUserId(sellTrade.getUserId());
		sellRecord.setTradeId(sellTrade.getId());
		sellRecord.setTradeType(sellTrade.getTradeType());
		sellRecord.setDealId(wdyDeal.getId());
		wdyTradeRecordService.save(sellRecord);

		List<WdyTxnHistory> txnHistoryList = new ArrayList<>();
		WdyTxnHistory txnHistory = new WdyTxnHistory();
		txnHistory.setUserId(buyUser.getUserId());
		txnHistory.setTxnId(String.valueOf(buyRecord.getId()));
		txnHistory.setTxnAmount(dealVc);
		txnHistory.setTxnType(TxnType.TRADE.getCode());
		txnHistory.setAmountType(AmountType.VC.getCode());
		txnHistory.setTxnDesc("交易买入VC");
		txnHistoryList.add(txnHistory);

		if(vcCommission.compareTo(BigDecimal.ZERO) > 0) {
			txnHistory = new WdyTxnHistory();
			txnHistory.setUserId(buyUser.getUserId());
			txnHistory.setTxnId(String.valueOf(buyRecord.getId()));
			txnHistory.setTxnAmount(BigDecimal.ZERO.subtract(vcCommission));
			txnHistory.setTxnType(TxnType.TRADE.getCode());
			txnHistory.setAmountType(AmountType.VC.getCode());
			txnHistory.setTxnDesc("买入VC手续费");
			txnHistoryList.add(txnHistory);
		}

		txnHistory = new WdyTxnHistory();
		txnHistory.setUserId(sellUser.getUserId());
		txnHistory.setTxnId(String.valueOf(sellRecord.getId()));
		txnHistory.setTxnAmount(dealCnn);
		txnHistory.setTxnType(TxnType.TRADE.getCode());
		txnHistory.setAmountType(AmountType.CNN_WITHDRAW.getCode());
		txnHistory.setTxnDesc("交易卖出VC");
		txnHistoryList.add(txnHistory);

		if(cnnCommission.compareTo(BigDecimal.ZERO) > 0) {
			txnHistory = new WdyTxnHistory();
			txnHistory.setUserId(sellUser.getUserId());
			txnHistory.setTxnId(String.valueOf(sellRecord.getId()));
			txnHistory.setTxnAmount(BigDecimal.ZERO.subtract(cnnCommission));
			txnHistory.setTxnType(TxnType.TRADE.getCode());
			txnHistory.setAmountType(AmountType.CNN_WITHDRAW.getCode());
			txnHistory.setTxnDesc("卖出VC手续费");
			txnHistoryList.add(txnHistory);
		}

		if(buyTrade.getTradingVc().compareTo(BigDecimal.ZERO) <= 0) {
			buyTrade.setTradeState(WdyTrade.STATE_FINISH);
			buyTrade.setFinishTime(new Date());
			this.updateById(buyTrade);
			if(buyTrade.getTradingCnn().compareTo(BigDecimal.ZERO) > 0) {
				buyUser = wdyUserService.getOne(buyUserQuery);
				updateUser = new WdyUser();
				updateUser.setId(buyUser.getId());
				updateUser.setCnnAmount(buyUser.getCnnAmount().add(buyTrade.getTradingCnn()));
				updateUser.setVersion(buyUser.getVersion());
				if(!wdyUserService.updateById(updateUser)) {
					throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
				}

				txnHistory = new WdyTxnHistory();
				txnHistory.setUserId(buyUser.getUserId());
				txnHistory.setTxnId(String.valueOf(buyTrade.getId()));
				txnHistory.setTxnAmount(buyTrade.getTradingCnn());
				txnHistory.setTxnType(TxnType.TRADE_BACK.getCode());
				txnHistory.setAmountType(AmountType.CNN.getCode());
				txnHistory.setTxnDesc(TxnType.TRADE_BACK.getName());
				txnHistoryList.add(txnHistory);

			}
		}

		if(sellTrade.getTradingVc().compareTo(BigDecimal.ZERO) <= 0) {
			sellTrade.setTradeState(WdyTrade.STATE_FINISH);
			sellTrade.setFinishTime(new Date());
			this.updateById(sellTrade);
		}

		wdyTxnHistoryService.saveBatch(txnHistoryList);

		return wdyDeal;
	}
	private void handleSell(WdyTrade sellTrade, BigDecimal vcPrice) {
		QueryWrapper<WdyTrade> queryWrapper = new QueryWrapper<>();
		queryWrapper.lt("id", sellTrade.getId());
		queryWrapper.ge("price", sellTrade.getPrice());
		queryWrapper.eq("trade_type", TradeType.BUY.getCode());
		queryWrapper.eq("trade_state", WdyTrade.STATE_TRADE);
		queryWrapper.orderByDesc("price");
		queryWrapper.orderByAsc("id", "create_time");
		List<WdyTrade> tradeList = this.list(queryWrapper);
		if(CollectionUtils.isEmpty(tradeList)) {
			return;
		}
		BigDecimal tradePrice = vcPrice;
		for(WdyTrade buyTrade : tradeList) {
			WdyDeal wdyDeal = makeDeal(buyTrade, sellTrade, tradePrice, TradeType.SELL.getCode());
			tradePrice = wdyDeal.getPrice();
			if(sellTrade.getTradingVc().compareTo(BigDecimal.ZERO) <= 0) {
				break;
			}
		}
		if(tradePrice.compareTo(vcPrice) != 0) {
			updateVcRatio(tradePrice);
		}
	}

	private void updateVcRatio(BigDecimal tradePrice) {
		SysConfig updateConfig = new SysConfig();
		updateConfig.setConfigValue(BigDecimalUtil.getP8Decimal(BigDecimalUtil.divide(1, tradePrice)).toString());
		QueryWrapper<SysConfig> updateWrapper = new QueryWrapper<>();
		updateWrapper.eq("config_key", CacheKeyConstants.VC_RATIO);
		sysConfigService.update(updateConfig, updateWrapper);
	}

	private void handleBuy(WdyTrade buyTrade, BigDecimal vcPrice) {
		QueryWrapper<WdyTrade> queryWrapper = new QueryWrapper<>();
		queryWrapper.lt("id", buyTrade.getId());
		queryWrapper.le("price", buyTrade.getPrice());
		queryWrapper.eq("trade_type", TradeType.SELL.getCode());
		queryWrapper.eq("trade_state", WdyTrade.STATE_TRADE);
		queryWrapper.orderByAsc("price", "id", "create_time");
		List<WdyTrade> tradeList = this.list(queryWrapper);
		if(CollectionUtils.isEmpty(tradeList)) {
			return;
		}
		BigDecimal tradePrice = vcPrice;
		for(WdyTrade sellTrade : tradeList) {
			WdyDeal wdyDeal = makeDeal(buyTrade, sellTrade, tradePrice, TradeType.BUY.getCode());
			tradePrice = wdyDeal.getPrice();
			if(buyTrade.getTradingVc().compareTo(BigDecimal.ZERO) <= 0) {
				break;
			}
		}
		if(tradePrice.compareTo(vcPrice) != 0) {
			updateVcRatio(tradePrice);
		}
	}

	@Transactional(rollbackFor = Exception.class)
	@Override
	public void cancel(Long tradeId) {
		WdyTrade trade = this.getById(tradeId);
		if(trade == null || trade.getTradeState() != WdyTrade.STATE_TRADE) {
			return;
		}

		if(trade.getTradeType() == TradeType.BUY.getCode()) {
			if(trade.getTradingCnn().compareTo(BigDecimal.ZERO) <= 0) {
				return;
			}
			QueryWrapper<WdyUser> userQuery = new QueryWrapper<>();
			userQuery.eq("user_id", trade.getUserId());
			WdyUser user = wdyUserService.getOne(userQuery);
			WdyUser updateUser = new WdyUser();
			updateUser.setId(user.getId());
			updateUser.setCnnAmount(user.getCnnAmount().add(trade.getTradingCnn()));
			updateUser.setVersion(user.getVersion());
			if(!wdyUserService.updateById(updateUser)) {
				throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
			}
			trade.setTradeState(WdyTrade.STATE_CANCEL);
			trade.setFinishTime(new Date());
			this.updateById(trade);

			WdyTxnHistory txnHistory = new WdyTxnHistory();
			txnHistory.setUserId(user.getUserId());
			txnHistory.setTxnId(String.valueOf(tradeId));
			txnHistory.setTxnAmount(trade.getTradingCnn());
			txnHistory.setTxnType(TxnType.TRADE_BACK.getCode());
			txnHistory.setAmountType(AmountType.CNN.getCode());
			txnHistory.setTxnDesc(TxnType.TRADE_BACK.getName());
			wdyTxnHistoryService.save(txnHistory);
		} else if(trade.getTradeType() == TradeType.SELL.getCode()) {
			if(trade.getTradingVc().compareTo(BigDecimal.ZERO) <= 0) {
				return;
			}
			QueryWrapper<WdyUser> userQuery = new QueryWrapper<>();
			userQuery.eq("user_id", trade.getUserId());
			WdyUser user = wdyUserService.getOne(userQuery);
			WdyUser updateUser = new WdyUser();
			updateUser.setId(user.getId());
			updateUser.setVcAmount(user.getVcAmount().add(trade.getTradingVc()));
			updateUser.setVersion(user.getVersion());
			if(!wdyUserService.updateById(updateUser)) {
				throw new BusinessException(ResultConstants.FI2007, "系统繁忙，请稍后重试");
			}
			trade.setTradeState(WdyTrade.STATE_CANCEL);
			trade.setFinishTime(new Date());
			this.updateById(trade);

			WdyTxnHistory txnHistory = new WdyTxnHistory();
			txnHistory.setUserId(user.getUserId());
			txnHistory.setTxnId(String.valueOf(tradeId));
			txnHistory.setTxnAmount(trade.getTradingVc());
			txnHistory.setTxnType(TxnType.TRADE_BACK.getCode());
			txnHistory.setAmountType(AmountType.VC.getCode());
			txnHistory.setTxnDesc(TxnType.TRADE_BACK.getName());
			wdyTxnHistoryService.save(txnHistory);
		}
	}

	@Override
	public List<Map<String,Object>> getBuyMarketDepth(String depth) {
		QueryWrapper<WdyTrade> queryWrapper = new QueryWrapper<>();
		queryWrapper.select("price", "SUM(trading_vc) AS amount", "COUNT(1) AS count");
		queryWrapper.eq("trade_state", WdyTrade.STATE_TRADE);
		queryWrapper.eq("trade_type", TradeType.BUY.getCode());
		queryWrapper.groupBy("price");
		queryWrapper.orderByDesc("price");
		queryWrapper.last("limit " + depth);
		List<Map<String, Object>> buyList = this.listMaps(queryWrapper);
		return buyList;
	}

	@Override
	public List<Map<String,Object>> getSellMarketDepth(String depth) {
		QueryWrapper<WdyTrade> queryWrapper = new QueryWrapper<>();
		queryWrapper.select("price", "SUM(trading_vc) AS amount", "COUNT(1) AS count");
		queryWrapper.eq("trade_state", WdyTrade.STATE_TRADE);
		queryWrapper.eq("trade_type", TradeType.SELL.getCode());
		queryWrapper.groupBy("price");
		queryWrapper.orderByAsc("price");
		queryWrapper.last("limit " + depth);
		List<Map<String, Object>> sellList = this.listMaps(queryWrapper);
		if(!org.springframework.util.CollectionUtils.isEmpty(sellList)) {
			Collections.reverse(sellList);
		}
		return sellList;
	}
}
